package org.mbds.wolf.db;

import org.mbds.wolf.exception.UserException;
import org.mbds.wolf.exception.UserExceptionReason;

/**
 *
 * @author amles_000
 */
class DataBase extends AtomicData{
    public final static short TABLES_ARRAY_SIZE_MAX = 256;
    public final static short TABLES_ARRAY_SIZE_INIT = 32;
    public final static short TABLES_ARRAY_SIZE_INC = 32;
    
    public final static byte[] DB_USER_TABLE_NAME = {'U', 'S', 'E', 'R'};
    public final static short DB_USER_TABLE_ID = 0x00;
    
    private DataTableUser dbUser = null;

    private DataTable[] tables = new DataTable[TABLES_ARRAY_SIZE_INIT];
    private short countTables = 0;
    
    /**
     * Constructor
     * @param id
     * @param name 
     */
    DataBase(short id, byte[] name) {
        super(id, name);
        createDbUser();
    }
    
    /**
     * countTables
     * @return count tables
     */
    short countTables() {
        return countTables;
    }
    

    /**
     * countColumns
     * @param tableId
     * @return count columns
     * @throws UserException
     */
    short countColumns(short tableId) throws UserException {
        DataTable t = getTableById(tableId);
        if (t==null)
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        return t.countColumns();
    }
    /**
     * countColumns
     * @param tableName
     * @return count columns
     * @throws UserException
     */
    short countColumns(byte[] tableName) throws UserException {
        DataTable t = getTableByName(tableName);
        if (t==null)
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        return t.countColumns();
    }
    /**
     * createDbUser
     * @return boolean
     */
    private boolean createDbUser() {
        try {
            createTable(DB_USER_TABLE_ID, DB_USER_TABLE_NAME);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    /**
     * getUserId
     * @param login
     * @return id
     * @throws UserException 
     */
    short getUserId(byte[] login) throws UserException {
        return dbUser.getUserId(login);
    }
    
    /**
     * checkUserPrivilege
     * @param login
     * @param privilegeId
     * @return boolean
     */
    boolean checkUserPrivilege(byte[] login, short privilegeId) {
        return dbUser.checkUserPrivilege(login, privilegeId);
    }
    
    /**
     * createTable
     * @param id
     * @param name
     * @throws UserException 
     */
    void createTable(final short id, final byte[] name) throws UserException {
        byte[] table = name;
//        byte[] table = null;
//        try {
//        //TODO: Table name should be char types without space only   
//            table = DataTool.trim(name);
//        } catch (UserException e) {
// //           UserException.throwIt(e.getReason());
//        }
        boolean isFoundId = searchTableById(id)>=0;
        boolean isFoundName = searchTableByName(table)>=0;
        if (isFoundId && isFoundName)
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_ALREADY_EXIST);
        if (isFoundId)
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_ID_ALREADY_USED);
        if (isFoundName)
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_NAME_ALREADY_USED);
        if ((countTables + 1 > tables.length) && !increaseTablesArray(TABLES_ARRAY_SIZE_INC))
            UserException.throwIt(UserExceptionReason.ARRAY_INDEX_OUT_OF_BOUND);
        DataTable t = null;
        if (id==DB_USER_TABLE_ID) {
            dbUser = new DataTableUser(DB_USER_TABLE_ID, DB_USER_TABLE_NAME);
            t = dbUser;
        } else {
            t = new DataTable(id, table);
        }
        tables[countTables] = t;
        countTables++;    
    }
    
    /**
     * addColumn
     * @param tableId
     * @param columnId
     * @param columnName
     * @throws UserException 
     */
    void addColumn(final short tableId, final short columnId, final byte[] columnName) throws UserException {
        DataTable tab = getTableById(tableId);
        if (tab==null) 
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        if (tab.isDeleted) 
            UserException.throwIt(UserExceptionReason.INVALID_UPDATE_DATA_DELETED);
        try {
            tab.addColumn(columnId, columnName);
        } catch (UserException e) {
            throw e;
        }
    }

    /**
     * addColumn
     * @param tableName
     * @param columnId
     * @param columnName
     * @throws UserException 
     */
    void addColumn(final byte[] tableName, final short columnId, final byte[] columnName) throws UserException {
        DataTable tab = getTableByName(tableName);
        if (tab==null) 
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        if (tab.isDeleted) 
            UserException.throwIt(UserExceptionReason.INVALID_UPDATE_DATA_DELETED);
        try {
            tab.addColumn(columnId, columnName);
        } catch (UserException e) {
            throw e;
        }
    }
    
    /**
     * select
     * @param tableId
     * @param columnId
     * @return AtomicData[] without deleted records
     */
    AtomicData[] select(short tableId, short columnId) {
        DataTable tab = getTableById(tableId);
        if (tab==null || tab.isDeleted) return null;
        return tab.select(columnId);
    }
    
    /**
     * select
     * @param tableId
     * @param columnId
     * @param recordId
     * @return byte[] value if exists and not deleted
     */
    byte[] select(short tableId, short columnId, short recordId) {
        DataTable tab = getTableById(tableId);
        if (tab==null || tab.isDeleted) return null;
        return tab.select(columnId, recordId);
    }
    
    /**
     * select
     * @param tableName
     * @param columnName
     * @return AtomicData[] without deleted records
     * @throws UserException 
     */
    AtomicData[] select(byte[] tableName, byte[] columnName) throws UserException {
        DataTable tab = getTableByName(tableName);
        if (tab==null || tab.isDeleted) return null;
        try {
            return tab.select(columnName);
        } catch (UserException e) {
            throw e;
        }
    }    

    /**
     * select
     * @param tableName
     * @param columnName
     * @param recordId
     * @return byte[] value if exists and not deleted
     * @throws UserException 
     */
    byte[] select(byte[] tableName, byte[] columnName, short recordId) throws UserException {
        DataTable tab = getTableByName(tableName);
        if (tab==null || tab.isDeleted) return null;
        try {
            return tab.select(columnName, recordId);
        } catch (UserException e) {
            throw e;
        }
    }    
    
    /**
     * insert
     * @param tableName
     * @param columnName
     * @param data
     * @return inserted record id
     * @throws UserException 
     */
    short insert(byte[] tableName, byte[] columnName, byte[] data) throws UserException  {
        DataTable tab = getTableByName(tableName);
         if (tab==null) 
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        if (tab.isDeleted) 
            UserException.throwIt(UserExceptionReason.INVALID_UPDATE_DATA_DELETED);
        try {        
            return tab.insert(columnName, data);
        } catch (UserException e) {
            throw e;
        }
    }

    /**
     * insert
     * @param tableId
     * @param columnId
     * @param record
     * @return inserted record id
     * @throws UserException 
     */
    short insert(short tableId, short columnId, byte[] record) throws UserException  {
        DataTable tab = getTableById(tableId);
         if (tab==null) 
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        if (tab.isDeleted) 
            UserException.throwIt(UserExceptionReason.INVALID_UPDATE_DATA_DELETED);
        try {        
            return tab.insert(columnId, record);
        } catch (UserException e) {
            throw e;
        }
    }
    
    /**
     * insert
     * @param tableName
     * @param columnName
     * @param bArraysOfRecords
     * @return nb records inserted
     * @throws UserException 
     */
    short insert(byte[] tableName, byte[] columnName, Object[] bArraysOfRecords) throws UserException  {
        DataTable tab = getTableByName(tableName);
         if (tab==null) 
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        if (tab.isDeleted) 
            UserException.throwIt(UserExceptionReason.INVALID_UPDATE_DATA_DELETED);
        try {        
            return tab.insert(columnName, bArraysOfRecords);
        } catch (UserException e) {
            throw e;
        }
    }
    
    /**
     * insert
     * @param tableId
     * @param columnId
     * @param bArraysOfRecords
     * @return nb records inserted
     * @throws UserException 
     */
    short insert(short tableId, short columnId, Object[] bArraysOfRecords) throws UserException  {
        DataTable tab = getTableById(tableId);
         if (tab==null) 
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        if (tab.isDeleted) 
            UserException.throwIt(UserExceptionReason.INVALID_UPDATE_DATA_DELETED);
        try {        
            return tab.insert(columnId, bArraysOfRecords);
        } catch (UserException e) {
            throw e;
        }
    }
    
    /**
     * delete
     * @param tableName
     * @param columnName
     * @param recordId
     * @return nb record deleted (0..1)
     * @throws UserException 
     */
    short delete(byte[] tableName, byte[] columnName, short recordId) throws UserException {
        DataTable tab = getTableByName(tableName);
        if (tab==null || tab.isDeleted) return 0;
        try {
            return tab.delete(columnName, recordId);
        } catch (UserException e) {
            throw e;
        }
    }

    /**
     * delete
     * @param tableId
     * @param columnId
     * @param recordId
     * @return nb records deleted
     */
    short delete(short tableId, short columnId, short recordId) {
        DataTable tab = getTableById(tableId);
        if (tab == null || tab.isDeleted) {
            return 0;
        }
        return tab.delete(columnId, recordId);
    }

    /**
     * update
     * @param tableId
     * @param columnId
     * @param recordId
     * @param newValue
     * @return nb records updated
     */
    short update(short tableId, short columnId, short recordId, byte[] newValue) {
        DataTable tab = getTableById(tableId);
        if (tab==null || tab.isDeleted) return 0;
        return tab.updateData(columnId, recordId, newValue);
    }

    /**
     * update
     * @param tableName
     * @param columnName
     * @param recordId
     * @param newValue
     * @return nb records updated
     * @throws UserException 
     */
    short update(byte[] tableName, byte[] columnName, short recordId, byte[] newValue) throws UserException {
        DataTable tab = getTableByName(tableName);
        if (tab==null || tab.isDeleted) return 0;
        try {
            return tab.updateData(columnName, recordId, newValue);
        } catch (UserException e) {
            throw e;
        }
    }

    /**
     * drop
     * @return boolean
     */
    boolean drop() {
        if (!isDeleted) return false;
        for (short i=0;i<tables.length;i++)
            tables[i].drop();
        isDeleted = true;
        return true;
    }
    
    /**
     * dropTable
     * @param tableName
     * @return boolean
     * @throws UserException 
     */
    boolean dropTable(byte[] tableName) throws UserException {
        short idx = -1;
        try {
            idx = searchTableByName(tableName);
        } catch (UserException e) {
            throw e;
        }
        if (idx<0) return false;
        return tables[idx].drop();
    }

    /**
     * dropTable
     * @param tableid
     * @return boolean
     */
    boolean dropTable(short tableid) {
        short idx = searchTableById(tableid);
        if (idx<0) return false;
        return tables[idx].drop();
    }
    
    /**
     * getTableById
     * @param tableId
     * @return DataTable or null
     */
    private DataTable getTableById(short tableId) {
        for (short i=0;i<tables.length;i++)
            if (tables[i]!=null && tables[i].id()==tableId)
                return tables[i];
        return null;
    }
    
    boolean checkUser(byte[] login, byte[] password) {
        return dbUser.checkUser(login, password);
    }
    
    /**
     * getTableByName
     * @param tableName
     * @return DataTable or null
     */
    private DataTable getTableByName(byte[] tableName) {
        for (short i=0;i<tables.length;i++)
            if (tables[i]!=null && tables[i].value().length==tableName.length
                    && DataTool.arrayCompare(tables[i].value, (short)0, tableName, (short)0, (short)tableName.length)==0)
                return tables[i];
        return null;
    }
    
    /**
     * dropColumn
     * @param tableId
     * @param columnName
     * @return 
     * @throws UserException 
     */
    boolean dropColumn(short tableId, byte[] columnName) throws UserException {
        DataTable tab = getTableById(tableId);
        if (tab==null) return false;
        try {
            return tab.dropColumnByName(columnName);
        } catch (UserException e) {
            throw e;
        }
    }
    
    /**
     * dropColumn
     * @param tableName
     * @param columnName
     * @return 
     * @throws UserException 
     */
    boolean dropColumn(byte[] tableName, byte[] columnName) throws UserException {
        DataTable tab = getTableByName(tableName);
        if (tab==null) return false;
        try {
            return tab.dropColumnByName(columnName);
        } catch (UserException e) {
            throw e;
        }
    }

    /**
     * dropColumn
     * @param tableId
     * @param columnId
     * @return boolean
     */
    boolean dropColumn(short tableId, short columnId) {
        DataTable tab = getTableById(tableId);
        if (tab==null) return false;
        return tab.dropColumnById(columnId);
    }
    /**
     * dropColumn
     * @param tableName
     * @param columnId
     * @return boolean
     */
    boolean dropColumn(byte[] tableName, short columnId) {
        DataTable tab = getTableByName(tableName);
        if (tab==null) return false;
        return tab.dropColumnById(columnId);
    }
    
    /**
     * searchTableById
     * @param id
     * @return short index, -1 if not found
     */
    short searchTableById(short id) {
        return DataTool.containsId(id, tables);
    }
    
    /**
     * searchTableByName
     * @param id
     * @return short index, -1 if not found
     * @throws UserException 
     */
    short searchTableByName(final byte[] name) throws UserException {
        try {
            return DataTool.containsValue(name, tables);
        } catch (UserException e) {
            throw e;
        }
     }    
    
    /**
     * increaseTablesArray
     * @param inc
     * @return boolean
     */
    private boolean increaseTablesArray(short inc) {
        if (isDeleted) return false;
        if (tables.length + inc > TABLES_ARRAY_SIZE_MAX) {
            return false;
        }
        DataTable[] newTables = new DataTable[(short) (tables.length + inc)];
        for (short i = 0; i < tables.length; i++) {
            newTables[i] = tables[i];
        }
        tables = newTables;
        return true;
    } 
    
    /**
     * rename
     * @param new name 
     * @throws UserException 
     */
    void rename(byte[] newName) throws UserException {
        if (isDeleted)
            UserException.throwIt(UserExceptionReason.INVALID_UPDATE_DATA_DELETED);
        if (searchTableByName(newName)<0)
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_NAME_ALREADY_USED);
        this.value = newName;
    }   
    
    /**
     * renameColumn
     * @param tableId
     * @param column id
     * @param new columns name 
     * @throws UserException 
     */
    boolean renameColumn(short tableId, short columnId, byte[] columnNewName) throws UserException {
        DataTable tab = getTableById(tableId);
        if (tab==null) return false;
        if (tab.isColumnNameAlreadyUsed(columnNewName))
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_NAME_ALREADY_USED);
        try {
            tab.renameColumn(columnId, columnNewName);
            return true;
        } catch (UserException e) {
            throw e;
        }
    }    
    /**
     * renameColumn
     * @param tableName
     * @param column id
     * @param new columns name 
     * @throws UserException 
     */
    boolean renameColumn(byte[] tableName, short columnId, byte[] columnNewName) throws UserException {
        DataTable tab = getTableByName(tableName);
        if (tab==null) return false;
        if (tab.isColumnNameAlreadyUsed(columnNewName))
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_NAME_ALREADY_USED);
        try {
            tab.renameColumn(columnId, columnNewName);
            return true;
        } catch (UserException e) {
            throw e;
        }
    }    
    /**
     * renameColumn
     * @param tableName
     * @param coulmnName
     * @param new columns name 
     * @throws UserException 
     */
    boolean renameColumn(byte[] tableName, byte[] coulmnName, byte[] columnNewName) throws UserException {
        DataTable tab = getTableByName(tableName);
        if (tab==null) return false;
        if (tab.isColumnNameAlreadyUsed(columnNewName))
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_NAME_ALREADY_USED);
        try {
            tab.renameColumn(coulmnName, columnNewName);
            return true;
        } catch (UserException e) {
            throw e;
        }
    }    
    /**
     * renameColumn
     * @param tableId
     * @param new table name 
     * @throws UserException 
     */
    boolean renameTable(short tableId, byte[] tableNewName) throws UserException {
        if (searchTableByName(tableNewName)>=0)
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_NAME_ALREADY_USED);
        DataTable tab = getTableById(tableId);
        if (tab==null) 
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        try {
            tab.rename(tableNewName);
            return true;
        } catch (UserException e) {
            throw e;
        }
    }    
    /**
     * renameColumn
     * @param tableName
     * @param new table name 
     * @throws UserException 
     */
    boolean renameTable(byte[] tableName, byte[] tableNewName) throws UserException {
        if (searchTableByName(tableNewName)>=0)
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_NAME_ALREADY_USED);
        DataTable tab = getTableByName(tableName);
        if (tab==null) 
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        try {
            tab.rename(tableNewName);
            return true;
        } catch (UserException e) {
            throw e;
        }
    }    
}
